#pragma once

#include "my_defs.h"
#include <arpa/inet.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#define MY_INET_ADDRSTRLEN          (sizeof("255.255.255.255") - 1)
#define MY_INET6_ADDRSTRLEN                                     \
    (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1)
#define MY_UNIX_ADDR_PATH_SIZE                                  \
    (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path))
#define MY_UNIX_ADDRSIZE                                        \
    (sizeof("unix:") - 1 + MY_UNIX_ADDR_PATH_SIZE)

#define MY_INET_SOCKADDR_SIZE       (MY_INET_ADDRSTRLEN + sizeof(":65535"))
#define MY_INET6_SOCKADDR_SIZE      (MY_INET6_ADDRSTRLEN + sizeof("[]:65535"))

// MAX(MY_INET_SOCKADDR_SIZE, MY_INET6_SOCKADDR_SIZE,  MY_UNIX_ADDRSTRLEN)
#define MY_SOCKADDR_SIZE            MY_UNIX_ADDRSIZE

#define MY_UNIX_ADDRESS_PREFIX      "unix:"
#define MY_UNIX_ADDRESS_PREFIX_LEN  (sizeof(MY_UNIX_ADDRESS_PREFIX) - 1)


typedef struct sockaddr         my_sockaddr_t;
typedef struct sockaddr_in      my_sockaddr_in_t;
typedef struct sockaddr_in6     my_sockaddr_in6_t;
typedef struct sockaddr_un      my_sockaddr_un_t;


// @return:
// if ret = 0, invalid addr.
// if ret = 1, addr is ipv4.
// if ret = 2, addr is ipv6.
// if ret = 3, addr is unix socket.
static inline int
my_inet_addr_check(const char *addr) {
    assert(addr != NULL);
    
    size_t addr_len = strlen(addr);
    if (addr_len > MY_SOCKADDR_SIZE - 1) {
        return 0;
    }

    if (addr_len > MY_UNIX_ADDRESS_PREFIX_LEN &&
        strncmp(addr, MY_UNIX_ADDRESS_PREFIX, MY_UNIX_ADDRESS_PREFIX_LEN) == 0) {
        return 3;
    }

    int res = 0;
    char buf[sizeof(struct in6_addr)];

    res = inet_pton(AF_INET, addr, buf);
    if (res == 1) {
        return 1;
    }

    res = inet_pton(AF_INET6, addr, buf);
    if (res == 1) {
        return 2;
    }

    return 0;
}


typedef union {
    my_sockaddr_t           sockaddr;
    my_sockaddr_in_t        sockaddr_in;
    my_sockaddr_in6_t       sockaddr_in6;
    my_sockaddr_un_t        sockaddr_un;
} my_address_t;


// only supports parsing address in the following format
// :port
// x.x.x.x or x.x.x.x:port
// [::] or [::]:port
// unix:path
// if the address does not contain port, it will be 0.
my_bool_t
my_address_parser(const char *addr_str, my_address_t *address, int *port);


static inline int
my_address_socklen(my_address_t *address)
{
    if (address->sockaddr.sa_family == AF_INET) {
        return sizeof(my_sockaddr_in_t);
    }

    if (address->sockaddr.sa_family == AF_INET6) {
        return sizeof(my_sockaddr_in6_t);
    }

    if (address->sockaddr.sa_family == AF_UNIX) {
        return sizeof(my_sockaddr_un_t);
    }

    return -1;
}


static inline my_bool_t
my_address_set_port(my_address_t *address, int port)
{
    if (address->sockaddr.sa_family == AF_INET) {
        address->sockaddr_in.sin_port = htons((uint16_t)port);
        return MY_TRUE;
    }

    if (address->sockaddr.sa_family == AF_INET6) {
        address->sockaddr_in6.sin6_port = htons((uint16_t)port);
        return MY_TRUE;
    }

    return MY_FALSE;
}

my_bool_t my_address_init_by_sockaddr(my_address_t *address, my_sockaddr_t *sockaddr);

int my_sockaddr_to_str(my_sockaddr_t *addr, char *dest, int dest_size);

static inline int
my_address_to_str(my_address_t *address, char *dest, int dest_size)
{
    return my_sockaddr_to_str(&address->sockaddr, dest, dest_size);
}